//
//    FILE: PulsePattern.cpp
//  AUTHOR: Rob dot Tillaart at gmail dot com  
// VERSION: see PULSEPATTERN_LIB_VERSION in .h
// PURPOSE: PulsePattern library for Arduino
//
// HISTORY:
// 0.0.1 - 2012-11-23 initial version
// 0.0.2 - 2012-11-23 adapted a static PPO
// 0.0.3 - 2012-12-27 renamed to PulsePattern
// 0.0.4 - 2012-12-27 code stable(?) enough to publish
// 0.0.5 - 2012-12-27 code cleanup+comment
//
// Released to the public domain
//
// TODO
// - fast function iso array to return the next period?
//   more adaptive to e.g. sensor values. (investigate)
// - test PRE 1.0 backwards compatibility
// - move code to .h file so compiler can inline?
// - optimize timer code
// - adjust timing to more accurate values -> setTimer()
// - worker should be private - how???
// - test invalid array periods
// - start en stop index ipv size?
// - pulsepattern recorder
//

#include "PulsePattern.h"

#if defined(ARDUINO) && ARDUINO >= 100
#include "Arduino.h"
#else
#include "WProgram.h"
#endif

// Predefined generator (singleton)
PulsePattern PPGenerator;

ISR(TIMER1_COMPA_vect)
{
  PPGenerator.worker();
}

PulsePattern::PulsePattern()
{
	_size = 0;
	_state = NOTINIT;
}

void PulsePattern::init(uint8_t pin, uint16_t * ar, uint8_t size, 
						uint8_t level, uint8_t prescaler)
{ 
	stop();
	_pin = pin;
	_ar = ar;
	_size = size;
	// TODO: run over the array to test invalid values? 
	// constrain them 10-4095?
	_level = constrain(level, LOW, HIGH);
	_prescaler = constrain(prescaler, PRESCALE_1, PRESCALE_1024);
	_cnt = 0;

	pinMode(_pin, OUTPUT);
	digitalWrite(_pin, _level);
}

void PulsePattern::start()
{
	if (_size == 0) return;  
	if (_state == RUNNING) return;  // no restart
	setTimer(1);  // start asap
	_state = RUNNING;
}

void PulsePattern::stop()
{
	stopTimer();
	_state = STOPPED;
	_level = LOW;
	digitalWrite(_pin, _level);
}

bool PulsePattern::isRunning()
{
	return (_state == RUNNING);
}

void PulsePattern::worker()
{
	if (_state != RUNNING) return;
	// set next period & flip signal
	_level = !_level;
	digitalWrite(_pin, _level);
	// TODO: adjustment needed for code overhead when micros?; 
	// + 5.2 usec for digitalWrite
	// + 3 usec for settimer call
	OCR1A = (_ar[_cnt]) * (F_CPU/1000000L);
	_cnt++;
	if (_cnt >= _size) _cnt = 0;  // repeat
}

// TIMER code based upon - http://www.gammon.com.au/forum/?id=11504
void PulsePattern::stopTimer()
{
	TCCR1A = 0;        // reset timer 1
	TCCR1B = 0;
}

// TODO: can be optimized?
void PulsePattern::setTimer(uint16_t cc)
{
	TCCR1A = 0;
	TCCR1B = 0;
	TCNT1 = 0;      		// reset counter
    OCR1A = cc*16;			// compare A register value;
							// *16 makes max period 4095
							// min period 12?
	
	// 4: CTC mode, top = OCR1A
	TCCR1A = _BV (COM1A1);  	// clear on compare
	TCCR1B = _BV (WGM12) | _prescaler;
	TIFR1 |= _BV (OCF1A);    // clear interrupt flag
	TIMSK1 = _BV (OCIE1A);   // interrupt on Compare A Match   
}

// END OF FILE